﻿using UnityEngine;
using System.Collections;

namespace RootMotion.Dynamics
{

    /// <summary>
    /// Automatically generates a ragdoll for a Biped character.
    /// </summary>
    [HelpURL("https://www.youtube.com/watch?v=y-luLRVmL7E&index=1&list=PLVxSIA1OaTOuE2SB9NUbckQ9r2hTg4mvL")]
    [AddComponentMenu("Scripts/RootMotion.Dynamics/Ragdoll Manager/Biped Ragdoll Creator")]
    public class BipedRagdollCreator : RagdollCreator
    {

        // Open the User Manual URL
        [ContextMenu("User Manual")]
        void OpenUserManual()
        {
            Application.OpenURL("http://root-motion.com/puppetmasterdox/html/page1.html");
        }

        // Open the Script Reference URL
        [ContextMenu("Scrpt Reference")]
        void OpenScriptReference()
        {
            Application.OpenURL("http://root-motion.com/puppetmasterdox/html/class_root_motion_1_1_dynamics_1_1_biped_ragdoll_creator.html#details");
        }

        // Open a video tutorial about setting up the component
        [ContextMenu("TUTORIAL VIDEO")]
        void OpenTutorial()
        {
            Application.OpenURL("https://www.youtube.com/watch?v=y-luLRVmL7E&index=1&list=PLVxSIA1OaTOuE2SB9NUbckQ9r2hTg4mvL");
        }

        public bool canBuild;
        public BipedRagdollReferences references;
        public Options options = Options.Default;

        [System.Serializable]
        public struct Options
        {

            //[SpaceAttribute(10)]

            public float weight;

            [HeaderAttribute("Optional Bones")]
            public bool spine;
            public bool chest;
            public bool hands;
            public bool feet;

            [HeaderAttribute("Joints")]
            public JointType joints;
            public float jointRange;

            [HeaderAttribute("Colliders")]
            public float colliderLengthOverlap;
            public ColliderType torsoColliders;
            public ColliderType headCollider;
            public ColliderType armColliders;
            public ColliderType handColliders;
            public ColliderType legColliders;
            public ColliderType footColliders;
            public bool fixFootColliderRotation;

            public static Options Default
            {
                get
                {
                    Options o = new Options();

                    // General Settings
                    o.weight = 75f;
                    o.colliderLengthOverlap = 0.1f;
                    o.jointRange = 1f;

                    o.chest = true;
                    o.headCollider = ColliderType.Capsule;
                    o.armColliders = ColliderType.Capsule;
                    o.hands = true;
                    o.handColliders = ColliderType.Capsule;
                    o.legColliders = ColliderType.Capsule;
                    o.feet = true;
                    o.fixFootColliderRotation = true;
                    return o;
                }
            }
        }

        public static Options AutodetectOptions(BipedRagdollReferences r)
        {
            Options o = Options.Default;

            if (r.spine == null) o.spine = false;
            if (r.chest == null) o.chest = false;

            // If chest bone is too high
            if (o.chest && Vector3.Dot(r.root.up, r.chest.position - GetUpperArmCentroid(r)) > 0f)
            {
                o.chest = false;
                if (r.spine != null) o.spine = true;
            }

            return o;
        }

        public static void Create(BipedRagdollReferences r, Options options)
        {
            string msg = string.Empty;
            if (!r.IsValid(ref msg))
            {
                Debug.LogWarning(msg);
                return;
            }

            // Clean up
            ClearAll(r.root);

            // Colliders
            CreateColliders(r, options);

            // Rigidbodies
            MassDistribution(r, options);

            // Joints
            CreateJoints(r, options);
        }

        private static void CreateColliders(BipedRagdollReferences r, Options options)
        {
            // Torso
            Vector3 upperArmToHeadCentroid = GetUpperArmToHeadCentroid(r);

            if (r.spine == null) options.spine = false;
            if (r.chest == null) options.chest = false;

            Vector3 shoulderDirection = r.rightUpperArm.position - r.leftUpperArm.position;
            float torsoWidth = shoulderDirection.magnitude;
            float torsoProportionAspect = 0.6f;

            // Hips
            Vector3 hipsStartPoint = r.hips.position;

            // Making sure the hip bone is not at the feet
            float toHead = Vector3.Distance(r.head.position, r.root.position);
            float toHips = Vector3.Distance(r.hips.position, r.root.position);

            if (toHips < toHead * 0.2f)
            {
                hipsStartPoint = Vector3.Lerp(r.leftUpperLeg.position, r.rightUpperLeg.position, 0.5f);
            }

            Vector3 lastEndPoint = options.spine ? r.spine.position : (options.chest ? r.chest.position : upperArmToHeadCentroid);
            hipsStartPoint += (hipsStartPoint - upperArmToHeadCentroid) * 0.1f;
            float hipsWidth = options.spine || options.chest ? torsoWidth * 0.8f : torsoWidth;

            CreateCollider(r.hips, hipsStartPoint, lastEndPoint, options.torsoColliders, options.colliderLengthOverlap, hipsWidth, torsoProportionAspect, shoulderDirection);

            // Spine
            if (options.spine)
            {
                Vector3 spineStartPoint = lastEndPoint;
                lastEndPoint = options.chest ? r.chest.position : upperArmToHeadCentroid;

                float spineWidth = options.chest ? torsoWidth * 0.75f : torsoWidth;

                CreateCollider(r.spine, spineStartPoint, lastEndPoint, options.torsoColliders, options.colliderLengthOverlap, spineWidth, torsoProportionAspect, shoulderDirection);
            }

            if (options.chest)
            {
                Vector3 chestStartPoint = lastEndPoint;
                lastEndPoint = upperArmToHeadCentroid;

                CreateCollider(r.chest, chestStartPoint, lastEndPoint, options.torsoColliders, options.colliderLengthOverlap, torsoWidth, torsoProportionAspect, shoulderDirection);
            }

            // Head
            Vector3 headStartPoint = lastEndPoint;
            Vector3 headEndPoint = headStartPoint + (headStartPoint - hipsStartPoint) * 0.45f;
            Vector3 axis = r.head.TransformVector(AxisTools.GetAxisVectorToDirection(r.head, headEndPoint - headStartPoint));
            headEndPoint = headStartPoint + Vector3.Project(headEndPoint - headStartPoint, axis).normalized * (headEndPoint - headStartPoint).magnitude;

            CreateCollider(r.head, headStartPoint, headEndPoint, options.headCollider, options.colliderLengthOverlap, Vector3.Distance(headStartPoint, headEndPoint) * 0.8f);

            // Arms
            float armWidthAspect = 0.4f;

            float leftArmWidth = Vector3.Distance(r.leftUpperArm.position, r.leftLowerArm.position) * armWidthAspect;

            CreateCollider(r.leftUpperArm, r.leftUpperArm.position, r.leftLowerArm.position, options.armColliders, options.colliderLengthOverlap, leftArmWidth);
            CreateCollider(r.leftLowerArm, r.leftLowerArm.position, r.leftHand.position, options.armColliders, options.colliderLengthOverlap, leftArmWidth * 0.9f);

            float rightArmWidth = Vector3.Distance(r.rightUpperArm.position, r.rightLowerArm.position) * armWidthAspect;

            CreateCollider(r.rightUpperArm, r.rightUpperArm.position, r.rightLowerArm.position, options.armColliders, options.colliderLengthOverlap, rightArmWidth);
            CreateCollider(r.rightLowerArm, r.rightLowerArm.position, r.rightHand.position, options.armColliders, options.colliderLengthOverlap, rightArmWidth * 0.9f);

            // Legs
            float legWidthAspect = 0.3f;

            float leftLegWidth = Vector3.Distance(r.leftUpperLeg.position, r.leftLowerLeg.position) * legWidthAspect;

            CreateCollider(r.leftUpperLeg, r.leftUpperLeg.position, r.leftLowerLeg.position, options.legColliders, options.colliderLengthOverlap, leftLegWidth);
            CreateCollider(r.leftLowerLeg, r.leftLowerLeg.position, r.leftFoot.position, options.legColliders, options.colliderLengthOverlap, leftLegWidth * 0.9f);

            float rightLegWidth = Vector3.Distance(r.rightUpperLeg.position, r.rightLowerLeg.position) * legWidthAspect;

            CreateCollider(r.rightUpperLeg, r.rightUpperLeg.position, r.rightLowerLeg.position, options.legColliders, options.colliderLengthOverlap, rightLegWidth);
            CreateCollider(r.rightLowerLeg, r.rightLowerLeg.position, r.rightFoot.position, options.legColliders, options.colliderLengthOverlap, rightLegWidth * 0.9f);

            // Hands
            if (options.hands)
            {
                CreateHandCollider(r.leftHand, r.leftLowerArm, r.root, options);
                CreateHandCollider(r.rightHand, r.rightLowerArm, r.root, options);
            }

            // Feet
            if (options.feet)
            {
                CreateFootCollider(r.leftFoot, r.leftLowerLeg, r.leftUpperLeg, r.root, options);
                CreateFootCollider(r.rightFoot, r.rightLowerLeg, r.rightUpperLeg, r.root, options);
            }
        }

        private static Collider CopyCollider(Collider c, GameObject destination)
        {
            if (c is CapsuleCollider)
            {
                var newCapsule = CopyCapsuleCollider(c as CapsuleCollider, destination);
                return newCapsule as Collider;
            }
            else if (c is SphereCollider)
            {
                var newSphere = CopySphereCollider(c as SphereCollider, destination);
                return newSphere as Collider;
            }
            else if (c is BoxCollider)
            {
                var newBox = CopyBoxCollider(c as BoxCollider, destination);
                return newBox as Collider;
            }
            return null;
        }

        private static CapsuleCollider CopyCapsuleCollider(CapsuleCollider o, GameObject destination)
        {
            CapsuleCollider d = destination.GetComponent<CapsuleCollider>();
            if (d == null) d = destination.AddComponent<CapsuleCollider>();

            d.isTrigger = o.isTrigger;
            d.sharedMaterial = o.sharedMaterial;
            d.center = o.center;
            d.radius = o.radius;
            d.height = o.height;
            d.direction = o.direction;

            return d;
        }

        private static SphereCollider CopySphereCollider(SphereCollider o, GameObject destination)
        {
            SphereCollider d = destination.GetComponent<SphereCollider>();
            if (d == null) d = destination.AddComponent<SphereCollider>();

            d.isTrigger = o.isTrigger;
            d.sharedMaterial = o.sharedMaterial;
            d.center = o.center;
            d.radius = o.radius;

            return d;
        }

        private static BoxCollider CopyBoxCollider(BoxCollider o, GameObject destination)
        {
            BoxCollider d = destination.GetComponent<BoxCollider>();
            if (d == null) d = destination.AddComponent<BoxCollider>();

            d.isTrigger = o.isTrigger;
            d.sharedMaterial = o.sharedMaterial;
            d.center = o.center;
            d.size = o.size;

            return d;
        }

        private static void CreateHandCollider(Transform hand, Transform lowerArm, Transform root, Options options)
        {
            Vector3 axis = hand.TransformVector(AxisTools.GetAxisVectorToPoint(hand, GetChildCentroid(hand, lowerArm.position)));

            Vector3 endPoint = hand.position - (lowerArm.position - hand.position) * 0.75f;
            endPoint = hand.position + Vector3.Project(endPoint - hand.position, axis).normalized * (endPoint - hand.position).magnitude;

            CreateCollider(hand, hand.position, endPoint, options.handColliders, options.colliderLengthOverlap, Vector3.Distance(endPoint, hand.position) * 0.5f);
        }

        private static void CreateFootCollider(Transform foot, Transform lowerLeg, Transform upperLeg, Transform root, Options options)
        {
            float legHeight = (upperLeg.position - foot.position).magnitude;
            Vector3 axis = foot.TransformVector(AxisTools.GetAxisVectorToPoint(foot, GetChildCentroid(foot, foot.position + root.forward) + root.forward * legHeight * 0.2f));

            Vector3 endPoint = foot.position + root.forward * legHeight * 0.25f;
            endPoint = foot.position + Vector3.Project(endPoint - foot.position, axis).normalized * (endPoint - foot.position).magnitude;

            float width = Vector3.Distance(endPoint, foot.position) * 0.5f;
            Vector3 startPoint = foot.position;

            bool footBelowRoot = Vector3.Dot(root.up, foot.position - root.position) < 0f;
            Vector3 heightOffset = footBelowRoot ? Vector3.zero : Vector3.Project((startPoint - root.up * width * 0.5f) - root.position, root.up);

            Vector3 direction = endPoint - startPoint;
            startPoint -= direction * 0.2f;

            if (options.fixFootColliderRotation)
            {
                Vector3 fAxis = AxisTools.GetAxisVectorToDirection(foot, root.forward);
                if (Vector3.Dot(foot.rotation * fAxis, root.forward) < 0f) fAxis = -fAxis;

                Vector3 normal = Vector3.up;
                Vector3 tangent = foot.rotation * fAxis;
                Vector3.OrthoNormalize(ref normal, ref tangent);

                Vector3 fallback = foot.position + tangent;
                Vector3 childCentroid = GetChildCentroidRecursive(foot, fallback);
                Vector3 toChildC = childCentroid - foot.position;

                var child = new GameObject("Foot Collider").transform;
                child.parent = foot;
                child.localPosition = Vector3.zero;
                child.localRotation = Quaternion.identity;
                var collider = CreateCollider(child, startPoint - heightOffset, endPoint - heightOffset, options.footColliders, options.colliderLengthOverlap, width, foot);

                child.rotation = Quaternion.FromToRotation(child.rotation * fAxis, childCentroid - child.position) * child.rotation;
                Orthogonize(child, root.forward, root.up);
                Orthogonize(child, root.right, root.up);

                if (childCentroid != fallback)
                {
                    Vector3 center = Vector3.Lerp(foot.position, childCentroid, 0.5f);
                    Vector3 colliderCenter = GetColliderCenter(collider);
                    child.position += center - colliderCenter;

                    float bottomY = GetColliderBottom(collider, root.up);
                    child.position += Vector3.up * (root.position.y - bottomY);
                }
            }
            else
            {
                CreateCollider(foot, startPoint - heightOffset, endPoint - heightOffset, options.footColliders, options.colliderLengthOverlap, width);
            }
        }

        public static CapsuleCollider FixColliderRotation(CapsuleCollider c)
        {
            if (c.transform.childCount == 0) return null;
            var child = c.transform.GetChild(0);
            if (child == null) return null;

            var toChildAxis = AxisTools.GetAxisToPoint(c.transform, child.position);
            Vector3 toChild = AxisTools.ToVector3(toChildAxis);
            float dot = Vector3.Dot(c.transform.rotation * toChild, child.position - c.transform.position);
            if (dot < 0f) toChild = -toChild;

            var newChild = new GameObject(c.gameObject.name + " Collider", typeof(CapsuleCollider)).transform;
            newChild.parent = c.transform;
            newChild.localPosition = Vector3.zero;
            newChild.localRotation = Quaternion.identity;

            var newCollider = CopyCapsuleCollider(c, newChild.gameObject);
            newChild.rotation = Quaternion.FromToRotation(newChild.rotation * toChild, child.position - c.transform.position) * newChild.rotation;

            DestroyImmediate(c);
            return newCollider;
        }

        public static Collider FixFootCollider(Transform foot, Transform root)
        {
            Vector3 fAxis = AxisTools.GetAxisVectorToDirection(foot, root.forward);
            if (Vector3.Dot(foot.rotation * fAxis, root.forward) < 0f) fAxis = -fAxis;

            Vector3 normal = Vector3.up;
            Vector3 tangent = foot.rotation * fAxis;
            Vector3.OrthoNormalize(ref normal, ref tangent);

            Vector3 fallback = foot.position + tangent;
            Vector3 childCentroid = GetChildCentroidRecursive(foot, fallback);
            Vector3 toChildC = childCentroid - foot.position;

            var child = new GameObject("Foot Collider").transform;
            child.parent = foot;
            child.localPosition = Vector3.zero;
            child.localRotation = Quaternion.identity;
            var footCollider = foot.GetComponent<Collider>();
            
            var collider = CopyCollider(footCollider, child.gameObject);

            if (Application.isPlaying)
            {
                Destroy(footCollider);
            }
            else
            {
                DestroyImmediate(footCollider);
            }

            child.rotation = Quaternion.FromToRotation(child.rotation * fAxis, childCentroid - child.position) * child.rotation;
            Orthogonize(child, root.forward, root.up);
            Orthogonize(child, root.right, root.up);

            if (childCentroid != fallback)
            {
                Vector3 center = Vector3.Lerp(foot.position, childCentroid, 0.5f);
                Vector3 colliderCenter = GetColliderCenter(collider);
                child.position += center - colliderCenter;

                float bottomY = GetColliderBottom(collider, root.up);
                child.position += Vector3.up * (root.position.y - bottomY);
            }

            return collider;
        }

        private static Vector3 GetColliderCenter(Collider c)
        {
            if (c is BoxCollider)
            {
                return c.transform.TransformPoint((c as BoxCollider).center);
            }
            if (c is CapsuleCollider)
            {
                return c.transform.TransformPoint((c as CapsuleCollider).center);
            }
            return c.transform.position;
        }

        private static float GetColliderBottom(Collider c, Vector3 up)
        {
            var t = c.transform;

            if (c is BoxCollider)
            {
                var box = c as BoxCollider;
                Vector3 axis = AxisTools.GetAxisVectorToDirection(t, -up);
                if (Vector3.Dot(t.rotation * axis, -up) < 0f) axis = -axis;

                var scaled = Vector3.Scale(box.size, axis * 0.5f);

                return (t.TransformPoint(box.center) + t.rotation * scaled).y;
            }

            if (c is CapsuleCollider)
            {
                var capsule = c as CapsuleCollider;
                Vector3 axis = AxisTools.GetAxisVectorToDirection(t, -up);
                if (Vector3.Dot(t.rotation * axis, -up) < 0f) axis = -axis;

                var scaled = capsule.radius * axis * 0.5f;

                return (t.TransformPoint(capsule.center) + t.rotation * scaled).y;
            }

            return GetColliderCenter(c).y;
        }

        private static void Orthogonize(Transform t, Vector3 direction, Vector3 normal)
        {
            Vector3 axis = AxisTools.GetAxisVectorToDirection(t, direction);
            if (Vector3.Dot(t.rotation * axis, direction) < 0f) axis = -axis;

            Vector3 tangent = t.rotation * axis;
            Vector3.OrthoNormalize(ref normal, ref tangent);
            t.rotation = Quaternion.FromToRotation(t.rotation * axis, tangent) * t.rotation;
        }

        private static Vector3 GetChildCentroidRecursive(Transform t, Vector3 fallback)
        {
            var children = t.GetComponentsInChildren<Transform>();
            if (children.Length < 2) return fallback;

            Vector3 c = Vector3.zero;
            for (int i = 1; i < children.Length; i++)
            {
                c += children[i].position;
            }
            c /= children.Length - 1;

            return c;
        }

        private static Vector3 GetChildCentroid(Transform t, Vector3 fallback)
        {
            if (t.childCount == 0) return fallback;

            Vector3 c = Vector3.zero;
            for (int i = 0; i < t.childCount; i++)
            {
                c += t.GetChild(i).position;
            }
            c /= (float)t.childCount;

            return c;
        }

        private static void MassDistribution(BipedRagdollReferences r, Options o)
        {
            int torsoBones = 3;
            if (r.spine == null)
            {
                o.spine = false;
                torsoBones--;
            }
            if (r.chest == null)
            {
                o.chest = false;
                torsoBones--;
            }

            float torsoPrc = 0.508f / (float)torsoBones;
            float headPrc = 0.0732f;
            float upperArmPrc = 0.027f;
            float lowerArmPrc = 0.016f;
            float handPrc = 0.0066f;
            float upperLegPrc = 0.0988f;
            float lowerLegPrc = 0.0465f;
            float footPrc = 0.0145f;

            r.hips.GetComponent<Rigidbody>().mass = torsoPrc * o.weight;
            if (o.spine) r.spine.GetComponent<Rigidbody>().mass = torsoPrc * o.weight;
            if (o.chest) r.chest.GetComponent<Rigidbody>().mass = torsoPrc * o.weight;

            r.head.GetComponent<Rigidbody>().mass = headPrc * o.weight;

            r.leftUpperArm.GetComponent<Rigidbody>().mass = upperArmPrc * o.weight;
            r.rightUpperArm.GetComponent<Rigidbody>().mass = r.leftUpperArm.GetComponent<Rigidbody>().mass;

            r.leftLowerArm.GetComponent<Rigidbody>().mass = lowerArmPrc * o.weight;
            r.rightLowerArm.GetComponent<Rigidbody>().mass = r.leftLowerArm.GetComponent<Rigidbody>().mass;

            if (o.hands)
            {
                r.leftHand.GetComponent<Rigidbody>().mass = handPrc * o.weight;
                r.rightHand.GetComponent<Rigidbody>().mass = r.leftHand.GetComponent<Rigidbody>().mass;
            }

            r.leftUpperLeg.GetComponent<Rigidbody>().mass = upperLegPrc * o.weight;
            r.rightUpperLeg.GetComponent<Rigidbody>().mass = r.leftUpperLeg.GetComponent<Rigidbody>().mass;

            r.leftLowerLeg.GetComponent<Rigidbody>().mass = lowerLegPrc * o.weight;
            r.rightLowerLeg.GetComponent<Rigidbody>().mass = r.leftLowerLeg.GetComponent<Rigidbody>().mass;

            if (o.feet)
            {
                r.leftFoot.GetComponent<Rigidbody>().mass = footPrc * o.weight;
                r.rightFoot.GetComponent<Rigidbody>().mass = r.leftFoot.GetComponent<Rigidbody>().mass;
            }
        }

        private static void CreateJoints(BipedRagdollReferences r, Options o)
        {
            // Torso
            if (r.spine == null) o.spine = false;
            if (r.chest == null) o.chest = false;

            float spineMinSwing = -30f * o.jointRange;
            float spineMaxSwing = 10f * o.jointRange;
            float spineSwing2 = 25f * o.jointRange;
            float spineTwist = 25f * o.jointRange;

            CreateJoint(new CreateJointParams(
                r.hips.GetComponent<Rigidbody>(),
                null,
                (o.spine ? r.spine : (o.chest ? r.chest : r.head)),
                r.root.right,
                new CreateJointParams.Limits(0f, 0f, 0f, 0f),
                o.joints
                ));

            if (o.spine)
            {
                CreateJoint(new CreateJointParams(
                    r.spine.GetComponent<Rigidbody>(),
                    r.hips.GetComponent<Rigidbody>(),
                    (o.chest ? r.chest : r.head),
                    r.root.right,
                    new CreateJointParams.Limits(spineMinSwing, spineMaxSwing, spineSwing2, spineTwist),
                    o.joints
                    ));
            }

            if (o.chest)
            {
                CreateJoint(new CreateJointParams(
                    r.chest.GetComponent<Rigidbody>(),
                    (o.spine ? r.spine.GetComponent<Rigidbody>() : r.hips.GetComponent<Rigidbody>()),
                    r.head,
                    r.root.right,
                    new CreateJointParams.Limits(spineMinSwing, spineMaxSwing, spineSwing2, spineTwist),
                    o.joints
                    ));
            }

            // Head
            Transform lastTorsoBone = o.chest ? r.chest : (o.spine ? r.spine : r.hips);

            CreateJoint(new CreateJointParams(
                r.head.GetComponent<Rigidbody>(),
                lastTorsoBone.GetComponent<Rigidbody>(),
                null,
                r.root.right,
                new CreateJointParams.Limits(-30f, 30f, 30f, 85f),
                o.joints
                ));

            // Arms
            CreateJointParams.Limits upperArmLimits = new CreateJointParams.Limits(-35f * o.jointRange, 120f * o.jointRange, 85f * o.jointRange, 45 * o.jointRange);
            CreateJointParams.Limits lowerArmLimits = new CreateJointParams.Limits(0f, 140f * o.jointRange, 10f * o.jointRange, 45f * o.jointRange);
            CreateJointParams.Limits handLimits = new CreateJointParams.Limits(-50f * o.jointRange, 50f * o.jointRange, 50f * o.jointRange, 25f * o.jointRange);

            // Left Arm
            CreateLimbJoints(
                lastTorsoBone,
                r.leftUpperArm,
                r.leftLowerArm,
                r.leftHand,
                r.root,
                -r.root.right,
                o.joints,
                upperArmLimits,
                lowerArmLimits,
                handLimits);

            // Right Arm
            CreateLimbJoints(
                lastTorsoBone,
                r.rightUpperArm,
                r.rightLowerArm,
                r.rightHand,
                r.root,
                r.root.right,
                o.joints,
                upperArmLimits,
                lowerArmLimits,
                handLimits);

            // Legs
            CreateJointParams.Limits upperLegLimits = new CreateJointParams.Limits(-120f * o.jointRange, 35f * o.jointRange, 85f * o.jointRange, 45 * o.jointRange);
            CreateJointParams.Limits lowerLegLimits = new CreateJointParams.Limits(0f, 140f * o.jointRange, 10f * o.jointRange, 45f * o.jointRange);
            CreateJointParams.Limits footLimits = new CreateJointParams.Limits(-50f * o.jointRange, 50f * o.jointRange, 50f * o.jointRange, 25f * o.jointRange);

            // Left Leg
            CreateLimbJoints(
                r.hips,
                r.leftUpperLeg,
                r.leftLowerLeg,
                r.leftFoot,
                r.root,
                -r.root.up,
                o.joints,
                upperLegLimits,
                lowerLegLimits,
                footLimits);

            // Right Leg
            CreateLimbJoints(
                r.hips,
                r.rightUpperLeg,
                r.rightLowerLeg,
                r.rightFoot,
                r.root,
                -r.root.up,
                o.joints,
                upperLegLimits,
                lowerLegLimits,
                footLimits);
        }

        private static void CreateLimbJoints(Transform connectedBone, Transform bone1, Transform bone2, Transform bone3, Transform root, Vector3 defaultWorldDirection, JointType jointType, CreateJointParams.Limits limits1, CreateJointParams.Limits limits2, CreateJointParams.Limits limits3)
        {
            Quaternion bone1DefaultLocalRotation = bone1.localRotation;

            bone1.rotation = Quaternion.FromToRotation(bone1.rotation * (bone2.position - bone1.position), defaultWorldDirection) * bone1.rotation;

            Vector3 bone1Dir = (bone2.position - bone1.position).normalized;
            Vector3 bone2Dir = (bone3.position - bone2.position).normalized;

            Vector3 bendPlaneNormal = -Vector3.Cross(bone1Dir, bone2Dir);
            float bone2PoseAngleOffset = Vector3.Angle(bone1Dir, bone2Dir);

            bool isVertical = Mathf.Abs(Vector3.Dot(bone1Dir, root.up)) > 0.5f;
            float verticalAngleOffsetMlp = isVertical ? 100f : 1f; // Fixing Mixamo's inverted legs

            // Fixing straight limbs
            if (bone2PoseAngleOffset < 0.01f * verticalAngleOffsetMlp)
            {
                if (isVertical) bendPlaneNormal = Vector3.Dot(bone1Dir, root.up) > 0f ? root.right : -root.right;
                else bendPlaneNormal = Vector3.Dot(bone1Dir, root.right) > 0f ? root.up : -root.up;

                //Debug.LogWarning("Limb " + bone1.name + ", " + bone2.name + ", " + bone3.name + " appears to be completely stretched out, Ragdoll Creator can not know how to assign joint limits. Please rotate the elbow/knee bone slightly towards its natural bending direction.");
            }

            CreateJoint(new CreateJointParams(
                bone1.GetComponent<Rigidbody>(),
                connectedBone.GetComponent<Rigidbody>(),
                bone2,
                bendPlaneNormal,
                limits1,
                jointType
                ));

            CreateJoint(new CreateJointParams(
                bone2.GetComponent<Rigidbody>(),
                bone1.GetComponent<Rigidbody>(),
                bone3,
                bendPlaneNormal,
                new CreateJointParams.Limits(limits2.minSwing - bone2PoseAngleOffset, limits2.maxSwing - bone2PoseAngleOffset, limits2.swing2, limits2.twist),
                jointType
                ));

            if (bone3.GetComponent<Rigidbody>() != null)
            {
                CreateJoint(new CreateJointParams(
                    bone3.GetComponent<Rigidbody>(),
                    bone2.GetComponent<Rigidbody>(),
                    null,
                    bendPlaneNormal,
                    limits3,
                    jointType
                    ));
            }

            bone1.localRotation = bone1DefaultLocalRotation;
        }

        public static void ClearBipedRagdoll(BipedRagdollReferences r)
        {
            var transforms = r.GetRagdollTransforms();
            foreach (Transform t in transforms) ClearTransform(t);
        }

        public static bool IsClear(BipedRagdollReferences r)
        {
            var transforms = r.GetRagdollTransforms();
            foreach (Transform t in transforms)
            {
                if (t.GetComponent<Rigidbody>() != null) return false;
            }
            return true;
        }

        private static Vector3 GetUpperArmToHeadCentroid(BipedRagdollReferences r)
        {
            return Vector3.Lerp(GetUpperArmCentroid(r), r.head.position, 0.5f);
        }

        private static Vector3 GetUpperArmCentroid(BipedRagdollReferences r)
        {
            return Vector3.Lerp(r.leftUpperArm.position, r.rightUpperArm.position, 0.5f);
        }
    }
}
